<html>
  <head>
    <title>迁移状态逻辑到Reducer中</title>
  </head>
  <script>
    // 迁移状态逻辑到Reducer（减速器）中
          /*
              对于拥有许多状态更新逻辑的组件来说，过于分散的事件处理程序可能会令人不只所措。
              对于这种情况，你可以将组件的所有状态更新逻辑整合到一个"外部函数"中，这个函数叫做"Reducer"
          */

          // 一、使用reducer整合状态逻辑
                    /*
                        随着组件复杂度的增加，你将很难一眼看清所有的组件状态更新逻辑。
                    */

                        // 例如，下面TaskApp组件有一个数组类型的状态tasks，并通过三个不同的事件处理程序来实现任务的添加、删除和修改
                              import {useState} from 'react';
                              import AddTask from './AddTask.js';
                              import TaskList from './TaskList.js'
                              
                              let  nextId = 3;

                              const initialTasks = [
                                  {id: 0, text: '参观卡夫卡博物馆', done: true},
                                  {id: 1, text: '看木偶戏', done: false},
                                  {id: 2, text: '打卡列侬墙', done: false}
                              ];

                              export default function TaskApp(){
                                  const [tasks,setTasks] = useState(initialTasks);

                                  // 添加任务
                                  function handleAddTask(text){
                                      setTasks([
                                        ...tasks,
                                        {
                                          id:nextId++,
                                          text:text,
                                          done:false
                                        }
                                      ]);
                                  }

                                  // 修改任务
                                  function handleChangeTask(task){
                                    setTasks(
                                      tasks.map(t=>{
                                        if(t.id===task.id){
                                          return task;
                                        }
                                        else{
                                          return t;
                                        }
                                      })
                                    );
                                  }

                                  // 删除任务
                                  function handleDeleteTask(taskId){
                                    setTasks(tasks.filter(t=>t.id !==taskId));
                                  }

                                  return (
                                    <>
                                        <h1>布拉格的形成安排</h1>
                                        <AddTask onAddTask={handleAddTask}/>
                                        <TaskList 
                                              task ={tasks}
                                              onChangeTask={handleChangeTask}
                                              onDeleteTask={handleDeleteTask}
                                        />
                                    </>
                                  );
                              }

                    /*
                        这个组件的每个事件处理程序都通过setTask状态设置函数来更新状态。
                        随着这个组件的不断迭代，其状态逻辑也会越来越多。
                        为了降低这种复杂度，并让所有逻辑可以存放在一个易于理解的地方，你可以将这些状态逻辑迁移到组件之外的一个称为"Reducer"的函数中。

                        Reduce是处理状态的另一种方式。你可以通过三个步骤将useSate迁移useReducer:

                                ①、将设置状态的逻辑"修改"成 dispatch的一个 action
                                ②、编写一个 reducer函数；
                                ③、在你的组件中使用 reducer
                    */

                    // 1、步骤1、从"设置状态"转移到"调度动作"
                              // 你的事件处理程序当前是通过"设置状态"来执行要执行的操作的；
                                  function handleAddTask(text){
                                      setTasks([
                                        ...tasks,
                                        {
                                          id:nextId++,
                                          text:text,
                                          done:false
                                        }
                                      ]);
                                  }

                              /*
                                  删除所有状态设置逻辑。你剩下的是三个事件处理程序：

                                      ①、当用户按下"添加"按钮时，调用 handleAddTask(text)
                                      ②、当用户切换人物或按下"保存"按钮时 调用 handleChangeTask(task)
                                      ③、当用户按下"删除"时调用 handleDeleteTask(taskId)

                                    
                                  使用Reducer管理状态与直接设置状态略有不同。
                                  不是通过设置状态来告诉React“做什么”，而是通过从事件处理程序中分派"操作(动作)"来指定"用户刚刚做了什么"。（状态更新逻辑将位于其他地方！）
                                  因此，你不是通过事件处理程序"设置tasks",而是调度"添加、修改、删除"动作。这样更能描述用于的意图。
                              */ 
                                    // dispatch：  单词中文含义： 分发、发送、派发

                                    // eg:
                                        function handleAddTask(text){
                                          dispatch({
                                            type:'added',
                                            id:nextId++,
                                            text:text
                                          });
                                        }

                                        function handleChageTask(task){
                                          dispatch({
                                            type:'changed',
                                            task:task
                                          });
                                        }

                                        function handleDeleteTask(taskId){
                                          dispatch({
                                            type:'deleted',
                                            id:taskId
                                          });
                                        }
                                  
                              /*
                                 你传递给 dispatch 的对象称为"操作(动作)"
                                 它是一个常规的JavaScript对象。你决定在其中放入什么，但通常它应该包含有关发生的事情的最少信息。（你将在后面的步骤中添加 dispatch函数本身）       

                                 注意：
                                        一个操作(动作)对象可以有任何形状。
                                        按照惯例，通常给它一个type字段来描述发生的事情，并在其他字段中传递任何附加信息。
                                        type特定于组件，因此在此示例中，'added'或者'added_task'都可以。选择一个能说明发生了什么的名字
                              */ 
                                       dispatch({
                                          // 特定于组件
                                          type:'what_happend'
                                       });
                    
                    // 2、步骤2：编写一个Reducer函数
                              /*
                                  Reducer函数时放置状态逻辑的地方。它有两个参数："当前状态"和"操作(动作)对象"，并返回下一个状态：
                              */
                                       function yourReducer(state,action){ // 第一个参数（state）：当前状态，  第二个参数（action）：操作(动作)对象
                                            // 返回新的状态(需要React设置的新的状态)
                                       }
                              /*
                                  React会将状态设置为你从Reducer函数返回的内容。

                                  在此示例中，要将状态设置逻辑从事件处理程序中移动到Reducer函数中，你将要：
                                       ①、将当前状态声明为第一个参数。
                                       ②、将操作(动作)对象声明为第二个参数。
                                       ③、从Reducer中返回下一个状态(React将状态设置为你返回的值)
                              */
                                       // 以下是迁移到Reducer函数的所有状态设置逻辑：
                                             function tasksReducer(tasks,action){
                                                   if(action.type==='added'){
                                                      return [
                                                        ...tasks,
                                                        {
                                                          id:action.id,
                                                          text:action.text,
                                                          done:false
                                                        }
                                                      ]
                                                   }
                                                   else if(action.type==='changed') {
                                                      return tasks.map(t=>{
                                                        if(t.id===action.task.id){
                                                          return action.task;
                                                        }
                                                        else {
                                                          return t;
                                                        }
                                                      })
                                                   }
                                                   else if(action.type==='deleted'){
                                                      return tasks.filter(t=>t.id!==action.id);
                                                   }
                                                   else{
                                                      throw Error('Unknown action:'+action.type);
                                                   }
                                             }
                                /*
                                    由于reducer 函数将状态(tasks)作为参数，因此你可以"在组件外"声明它。这会减少缩进级别，并使代码更易于阅读。

                                   
                                */
                                /*
                                     注意：
                                         上面的代码使用了if/else语句，但在 reducer中使用switch语句是一种"惯例"。结果是一样的，但是一目了然地阅读switch语句会更容易。
                                */
                                    // eg:
                                          function tasksReducer(tasks,action){
                                            switch(action.type){
                                              case 'added':{
                                                return [
                                                  ...tasks,
                                                  {
                                                    id:action.id,
                                                    text:action.text,
                                                    done:false
                                                  }
                                                ]
                                              }
                                              case 'changed':{
                                                return tasks.map(t=>{
                                                  if(t.id===action.task.id){
                                                    return action.task;
                                                  }
                                                  else{
                                                    return t;
                                                  }
                                                })
                                              }
                                              case 'deleted':{
                                                return tasks.filter(t=>t.id!==action.id);
                                              }
                                              default:{
                                                throw Error('Unknown action：'+action.type);
                                              }
                                            }
                                          }

                                    /*
                                        我们建议将每个 case 块封装在"{}"大括号中，以便在不同 case内声明的变量不会相互冲突。

                                        此外，case通常应该以 retrun 结尾。如果你忘记了 return,代码会把"落空"变成下一个 case ，这会导致错误。
                                        如果你还不习惯使用 switch语句，那么使用 if/else 完全没问题。
                                    */


                  // 3、步骤3：使用组件的reducer
                                // 最后，你需要将 tasksReducer 连接到你的组件。
                                        // 从 React 导入 useReducer钩子：
                                            import {useReducer} from 'react';
                                        // 然后替换 useState钩子：
                                            // const [tasks,setTasks] =useState(initialTasks);
                                            // 替换成：
                                            const [tasks,dispatch]= useReducer(tasksReducer,initialTasks);
                                        /*
                                            useReducer 钩子与 useState 类似，你必须向他传递"状态的初始值"和"设置状态的函数"(在本例中是调度函数)

                                            useReducer钩子有两个参数：
                                                ①、reducer 函数
                                                ②、初始状态
                                            
                                            它返回：
                                                ①、有值的状态
                                                ②、调度函数(用于"调度"用户操作到 reducer)
                                        */
                                        //  现在它已经完全连接起来了！ 这里，reducer 在组件文件的底部声明：
                                                // app.js 文件：
                                                import {useReducer} from 'react';
                                                import AddTask from './AddTask.js';
                                                import TaskList from './TaskList.js';

                                                // reducer 函数：
                                                function tasksReducer(tasks,action){
                                                    switch(action.type){
                                                        case 'added':{
                                                          return [
                                                            ...tasks,
                                                            {
                                                              id:action.id,
                                                              text:action.text,
                                                              done:false
                                                            }
                                                          ]
                                                        }
                                                        case 'changed':{
                                                          return tasks.map(t=>{
                                                            if(t.id===action.task.id){
                                                              return action.task;
                                                            }
                                                            else{
                                                              return t;
                                                            }
                                                          })
                                                        }
                                                        case 'deleted':{
                                                           return tasks.filter(t=>t.id !== action.id)
                                                        }

                                                    }
                                                }

                                                let nextId = 3;
                                                const initialTasks = [
                                                  {id: 0, text: 'Visit Kafka Museum', done: true},
                                                  {id: 1, text: 'Watch a puppet show', done: false},
                                                  {id: 2, text: 'Lennon Wall pic', done: false},
                                                ];

                                                export default function TaskApp(){
                                                    const [tasks,dispatch] = useReducer(tasksReducer,initialTasks);

                                                    function handleAddTask(text){
                                                        dispatch({
                                                          type:'added',
                                                          id: nextId++,
                                                          text:text
                                                        });
                                                    }

                                                    function handleChangeTask(task){
                                                        dispatch({
                                                          type:'changed',
                                                          id:task.id
                                                        });
                                                    }

                                                    function handleDeleteTask(taskId){
                                                        dispatch({
                                                          type:'deleted',
                                                          id:taskId
                                                        });
                                                    }

                                                    return (
                                                      <>
                                                          <h1>Prague itinerary</h1> 
                                                          <AddTask onAddTask={handleAddTask}/>
                                                          <TaskList tasks={tasks} onChangeTask={handleChageTask} onDeleteTask={handleDeleteTask} />
                                                      </>

                                                    );
                                                }

            // 二、比较useState 和 useReducer
                      /*
                          reducer 并非没有缺点！ 你可以通过以下几种方式比较它们：
                                  ①、代码量小：  通常，对于useState，你必须预先编写更少的代码。对于useReducer，你必须同时编写reducer函数和调度操作。
                                             但是，如果许多事件处理程序一类似的方式修改状态，useReducer 
                                              (意思是，有的用户操作对应着多个动作，这时候，useReducer 更加合适。)
                                  ②、可读性：  当状态很简单时，useState非常容易阅读。当他们变得更复杂时，他们会使你的鹅足腱代码膨胀并使其难以扫描。
                                            在这种情况下，useReducer 可以让你清楚地将更新逻辑的方式与事件处理程序分开。

                                  ③、调试性： 当你遇到useState错误时，可能很难判断状态设置错误的位置以及原因。使用useReducer,你可以在reducer中添加一个控制台日志，
                                            以查看每个状态的更新，以及它发生的原因(由于哪个action引起的)。如果每个action都是正确的，你就会知道错误出在 reducer逻辑本身。

                                  ④、测试reducer 是一个不依赖于你组件的纯函数。这意味着你可以隔离地单独导出和测试它。虽然通常最好在更真实的环境中测试组件，但对于复杂的状态更新逻辑，
                                             断言你的reducer 为特定的初始状态和操作返回特定的状态可能很有用。
                                  ⑤、个人喜好：  有些人喜欢reducer 有些人不喜欢。没关系，这是一个偏好问题。你始终可以在 useState 和 useReducer 之间来回切换：它们是等价的！

                                  如果你经常遇到一些组件由于状态更细腻不正确而导致的错误，并且想在其代码中引入更多结构，我们建议使用reducer.
                                  你不必为所有事情都是用reducer: 可以随意混搭！你甚至可以在同一个组件中使用 useState 和 useReducer.
)
                      */


            // 三、写好reducer  
                      /*
                          编写reducer 时请牢记这两个提示：
                                １、Reducer必须是纯粹的。
                                        与"状态更新函数"类似，reducer在渲染过程中执行！（操作会排队知道下一次渲染）
                                        这意味着 reducer 必须是纯粹的----相同的输入总是产生相同的输出。
                                        它们不应该发送请求、安排超时或执行任何副作用(影像组件外部事物的操作)。
                                        它们应该"无突变"地更新对象和数组。

                                2、每个操作都描述了"一次用户交互"，即使这会导致数据发生多次更改。
                                        例如，如果用户在具有由reducer管理的五个字段的表单上点击"重置"，则分派一个 reset_form 操作，比分派五个单独的set_field操作更有意义。
                                        如果你记录reducer中的每个操作，Name该日志应该足够清晰，以便你重建以何种顺序发生的交互或响应。这有助于调试！。
                      */

            // 四、使用Immer 编写简洁的reducer
                      /*
                          就像 "更新对象" 和 "数组"处于常规状态一样，你可以使用 Immer 库使reducer更简洁。

                          在这里， useImmerReducer 钩子允许你使用push() 或者 arr[i]=  等突变方法来更新数组和对象。
                      */
                              import { useImmerReducer} from 'user-immer';
                              import AddTask from './AddTask.js';
                              import TaskList from './TaskList.js';

                              let nextId = 3;
                              const initialTasks = [
                                {id: 0, text: 'Visit Kafka Museum', done: true},
                                {id: 1, text: 'Watch a puppet show', done: false},
                                {id: 2, text: 'Lennon Wall pic', done: false},
                              ];

                              function tasksReducer(draft,action){
                                 switch(action.type){
                                    case 'added':{
                                      draft.push({
                                        id:action.id,
                                        text:action.text,
                                        done:false
                                      });
                                    }
                                    
                                    case 'changed':{
                                       const index = draft.findIndex(t=>t.id===action.task.id);
                                       draft[index] =action.task;
                                       break;
                                    }

                                    case 'deleted':{
                                       return draft.filter(t=>t.id!==action.id)
                                    }

                                    default:{
                                      throw Error('Unknown action:'+action.type);
                                    }
                                 }
                              }

                              export default function TaskApp(){
                                   const [tasks,dispatch] =useImmerReducer(tasksReducer,initialTasks);

                                   function handleAddTask(text){
                                        dispatch({
                                            type:'added',
                                            id:nextId++,
                                            text:text
                                        });
                                   }

                                   function handleChangeTask(task){
                                        dispatch({
                                            type:'changed',
                                            task:task
                                        });
                                   }

                                   function handleDeleteTask(taskId){
                                        dispatch({
                                            type:'deleted',
                                            id:taskId
                                        });
                                   }

                                   return (
                                            <>
                                              <h1>Prague itinerary</h1>
                                              <AddTask onAddTask={handleAddTask} />
                                              <TaskList
                                                tasks={tasks}
                                                onChangeTask={handleChangeTask}
                                                onDeleteTask={handleDeleteTask}
                                              />
                                            </>
                                  );
                              }
                    /*
                        Reducer 必须是纯粹的，它返回用于更新状态的新的状态对象，但它们不应该改变状态。
                        但是Immer为你提供了一个可以安全修改的特殊draft对清。
                         在引擎盖下，Immerer将使用你对draft所做的更改擦混关键你的状态副本。这就是为什么由 useImmerReducer 管理下的reducer可改变它们的第一个参数并且不需要返回状态。
                    */            
                            
  </script>
</html>